import http.uv
import strings
import co
import fmt
import http.utils
import runtime

u8 HTTP_DELETE = 0
u8 HTTP_GET = 1
u8 HTTP_HEAD = 2
u8 HTTP_POST = 3
u8 HTTP_PUT = 4
u8 HTTP_CONNECT = 5
u8 HTTP_OPTIONS = 6
u8 HTTP_TRACE = 7
u8 HTTP_COPY = 8
u8 HTTP_LOCK = 9
u8 HTTP_MKCOL = 10
u8 HTTP_MOVE = 11
u8 HTTP_PROPFIND = 12
u8 HTTP_PROPPATCH = 13
u8 HTTP_SEARCH = 14
u8 HTTP_UNLOCK = 15
u8 HTTP_BIND = 16
u8 HTTP_REBIND = 17
u8 HTTP_UNBIND = 18
u8 HTTP_ACL = 19
u8 HTTP_REPORT = 20
u8 HTTP_MKACTIVITY = 21
u8 HTTP_CHECKOUT = 22
u8 HTTP_MERGE = 23
u8 HTTP_MSEARCH = 24
u8 HTTP_NOTIFY = 25
u8 HTTP_SUBSCRIBE = 26
u8 HTTP_UNSUBSCRIBE = 27
u8 HTTP_PATCH = 28
u8 HTTP_PURGE = 29
u8 HTTP_MKCALENDAR = 30
u8 HTTP_LINK = 31
u8 HTTP_UNLINK = 32
u8 HTTP_SOURCE = 33
u8 HTTP_PRI = 34
u8 HTTP_DESCRIBE = 35
u8 HTTP_ANNOUNCE = 36
u8 HTTP_SETUP = 37
u8 HTTP_PLAY = 38
u8 HTTP_PAUSE = 39
u8 HTTP_TEARDOWN = 40
u8 HTTP_GET_PARAMETER = 41
u8 HTTP_SET_PARAMETER = 42
u8 HTTP_REDIRECT = 43
u8 HTTP_RECORD = 44
u8 HTTP_FLUSH = 45
u8 HTTP_QUERY = 46

int ROUTES_GET = 0
int ROUTES_POST = 1
int ROUTES_PUT = 2
int ROUTES_DELETE = 3
int ROUTES_PATCH = 4
int ROUTES_OPTIONS = 5
int ROUTES_HEAD = 6
int ROUTES_ALL = 7

var HTTP_VERSION = 'HTTP/1.1'
var HTTP_MSG_OK = 'OK'

var MIME_TEXT = 'text/plain'
var MIME_JSON = 'application/json'
var separation = '\r\n' as anyptr as [u8]
var separation2 = '\r\n\r\n' as anyptr as [u8]

type callback_fn = fn(request_t, ptr<response_t>):void!

fn callback_notfound(request_t req, ptr<response_t> res):void! {
    res.status = 404
    res.message = 'Not Found'
    res.send('404 not found')
}

#runtime_use n_server_t
type server_t = struct{
    fn():void! handler_fn
    string addr
    int port
    [{string:callback_fn};8] routers

    [response_t] resonse_pool 

    anyptr inner
}

fn server_t.acquire_response() {
    
}

fn server_t.get(string path, callback_fn callback) {
    self.router_register(ROUTES_GET, path, callback)
}

fn server_t.post(string path, callback_fn callback) {
    self.router_register(ROUTES_POST, path, callback)
}

fn server_t.put(string path, callback_fn callback) {
    self.router_register(ROUTES_PUT, path, callback)
}

fn server_t.delete(string path, callback_fn callback) {
    self.router_register(ROUTES_DELETE, path, callback)
}

fn server_t.patch(string path, callback_fn callback) {
    self.router_register(ROUTES_PATCH, path, callback)
}

fn server_t.options(string path, callback_fn callback) {
    self.router_register(ROUTES_OPTIONS, path, callback)
}

fn server_t.head(string path, callback_fn callback) {
    self.router_register(ROUTES_HEAD, path, callback)
}

fn server_t.all(string path, callback_fn callback) {
    self.router_register(ROUTES_ALL, path, callback)
}

fn server_t.router_register(int routes_type, string path, callback_fn callback) {
    assert(routes_type <= 7 && routes_type >= 0)
    self.routers[routes_type][path] = callback
}

fn server_t.find_callback(u8 method, string path):callback_fn {
    int routes_type = match method {
        HTTP_GET -> ROUTES_GET
        HTTP_POST -> ROUTES_POST
        HTTP_PUT -> ROUTES_PUT
        HTTP_DELETE -> ROUTES_DELETE
        HTTP_PATCH -> ROUTES_PATCH
        HTTP_OPTIONS -> ROUTES_OPTIONS
        _ -> ROUTES_ALL
    }

    var routes = self.routers[routes_type]
    if routes.contains(path) {
        return routes[path]
    }

    var all_routes = self.routers[ROUTES_ALL]
    if all_routes.contains(path) {
        return all_routes[path]
    }

    return all_routes['/'] catch e {
       return callback_notfound as callback_fn
    }
}

fn server_t.listen(int port):void! {
    self.port = port
    uv.http_listen(self as anyptr)
}

fn server_t.close() {
    uv.http_close(self as anyptr)
}

fn server():ptr<server_t> {
    var s = new server_t(addr = '0.0.0.0', handler_fn = conn_handler)
    return s
}

type request_t = struct{
    u8 method
    {string:string} headers
    int length // content length
    string host
    string url // / /test?key=value
    string path // 获取 URL 的路径部分。 /test or /test/
    string query // key=value&k2=v2
    string body // http 请求原始 body
}

type response_t = struct{
    string version // http 版本号
    {string:string} headers
    int status // http 状态码
    string message // 默认情况下 http 状态码对应的消息
    int length // content length
    string body // 响应体
    string content_type // content type
}

fn request_new(rawptr<uv.conn_t> conn):request_t {
    var req = request_t{
        method: conn.method,
        host: runtime.string_ref_new(conn.host_at, conn.host_len),
        url: runtime.string_ref_new(conn.url_at, conn.url_len),
        path: runtime.string_ref_new(conn.path_at, conn.path_len),
    }

    if conn.body_len > 0 {
        req.length = conn.body_len
        req.body = runtime.string_ref_new(conn.body_at, conn.body_len)
    }

    if conn.query_len > 0 {
        req.query = runtime.string_ref_new(conn.query_at, conn.query_len)
    }

    for int i = 0; i < conn.headers_len; i += 1 {
        var name = runtime.string_ref_new(conn.headers[i].name_at, conn.headers[i].name_len)
        var value = runtime.string_ref_new(conn.headers[i].value_at, conn.headers[i].value_len)
        req.headers[name] = value
    }

    return req
}

// call in new coroutine
fn conn_handler():void! {
    var arg = co.arg()
    if arg == 0 {
        panic('conn_handler arg is null')
        // throw errorf('conn_handler arg is null')
    }
    var conn = arg as rawptr<uv.conn_t>

    var req = request_new(conn)
    var res = response_new()

    var handler = conn.server.find_callback(req.method, req.path)
    handler(req, res) catch e {
        res.status = 500
        res.send('Internal error')
    }
    uv.conn_resp(conn, res.to_str())
}

fn response_new():ptr<response_t> {
    return new response_t(
        headers = {'Connection': 'close'},
        status = 200,
        message = HTTP_MSG_OK,
        length = 0,
        content_type = MIME_TEXT,
    )
}

fn response_t.send(string msg) {
    self.body = msg
    self.length = msg.len()
}

fn response_t.to_str():string {
    var cap = (100 + self.body.len()) * 2
    var buf = vec_cap<u8>(cap)

    u8 space_ascii = 32

    buf.append(HTTP_VERSION as anyptr as [u8])
    buf.push(space_ascii)
    buf.append(utils.status_to_str(self.status) as anyptr as [u8])
    buf.push(space_ascii)
    buf.append(self.message as anyptr as [u8])
    buf.append(separation)

    var header_len = self.headers.len()
    for k, v in self.headers {
        buf.append(k as anyptr as anyptr as [u8])
        buf.append(': ' as anyptr as anyptr as [u8])
        buf.append(v as anyptr as anyptr as [u8])
        buf.append(separation)
    }

    buf.append('Content-Length: ' as anyptr as [u8])
    buf.append(utils.utos(self.length as uint) as anyptr as [u8])
    buf.append(separation)

    buf.append('Content-Type: ' as anyptr as [u8])
    buf.append(self.content_type as anyptr as [u8])
    buf.append('; charset=utf-8' as anyptr as [u8])

    buf.append(separation2)
    buf.append(self.body as anyptr as [u8])

    return buf as anyptr as string
}
